package org.homonoia.controller.core;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Properties;

import org.apache.log4j.Logger;
import org.homonoia.controller.exceptions.StartupException;
import org.homonoia.controller.queue.JobQueue;
import org.homonoia.model.core.NodeStatus;

public class ControllerNodeService implements Runnable {

    private static final Logger log = Logger.getLogger(ControllerNodeService.class);

    private Properties configProps = null;

    private JobQueue jobQueue = null;
    private WorkerManager workerManager = null;

    volatile private boolean stopped = true;

    public void loadConfiguration() throws StartupException {
	configProps = new Properties();
	try {
	    configProps.load(new FileReader("conf/configuration.properties"));
	} catch (FileNotFoundException e) {
	    log.error(e.getMessage(), e);
	    throw new StartupException(e.getMessage(), e.getCause());
	} catch (IOException e) {
	    log.error(e.getMessage(), e);
	    throw new StartupException(e.getMessage(), e.getCause());
	}

	try {
	    Class.forName(configProps.getProperty("DBDriver")).newInstance();
	} catch (ClassNotFoundException e) {
	    log.error(e.getMessage(), e);
	    throw new StartupException(e.getMessage(), e.getCause());
	} catch (InstantiationException e) {
	    log.error(e.getMessage(), e);
	    throw new StartupException(e.getMessage(), e.getCause());
	} catch (IllegalAccessException e) {

	    throw new StartupException(e.getMessage(), e.getCause());
	}
    }

    public void initializeManagers() {
	jobQueue = new JobQueue(configProps);
	workerManager = new WorkerManager(
		Integer.parseInt(configProps.getProperty("TimeoutPeriod")));
    }

    public void run() {
	stopped = false;

	long lTimeLastUpdate = 0L;
	while (!stopped) {
	    final long lTimeCurrentUpdate = Calendar.getInstance().getTimeInMillis();
	    final long lTimeSinceLastUpdate = lTimeCurrentUpdate - lTimeLastUpdate;

	    if (lTimeSinceLastUpdate > 200) {
		ArrayList<String> timedoutNodes = workerManager.getTimedoutNodes();
		if (timedoutNodes.size() > 0) {
		    this.queryTimedNodes(timedoutNodes);
		}

		while (workerManager.getFreeNodeCount() > 0) {
		    String node = workerManager.getNextFreeNode();
		    if (node != null && jobQueue.runNextTask(node)) {
			workerManager.updateWorkerStatus(node, NodeStatus.BUSY);
		    } else {
			break;
		    }
		}

		jobQueue.pruneCompletedJobs();

		lTimeLastUpdate = lTimeCurrentUpdate;
	    } else {
		try {
		    Thread.sleep(100);
		} catch (InterruptedException e) {
		    log.error(e.getMessage(), e);
		}
	    }
	}
    }

    public void stop() {
	stopped = true;
    }

    private void queryTimedNodes(final ArrayList<String> timedoutNodes) {
	int port = Integer.parseInt(configProps.getProperty("WorkerPort"));

	Socket sock = null;
	PrintWriter out = null;
	BufferedReader in = null;
	String response = null;

	for (String s : timedoutNodes) {
	    try {
		sock = new Socket(s, port);

		if (sock.isConnected()) {
		    out = new PrintWriter(sock.getOutputStream(), true);
		    in = new BufferedReader(new InputStreamReader(sock.getInputStream()));

		    out.println("Request: application/status");
		    response = in.readLine();

		    if (response.contains("AVAILABLE")) {
			workerManager.updateWorkerStatus(s, NodeStatus.AVAILABLE);
		    } else if (response.contains("BUSY")) {
			workerManager.updateWorkerStatus(s, NodeStatus.BUSY);
		    } else {
			workerManager.updateWorkerStatus(s, NodeStatus.UNKNOWN);
			jobQueue.rescheduleTasksOnNode(s);
		    }
		}
	    } catch (UnknownHostException e) {
		log.error(e.getMessage(), e);
		workerManager.removeWorker(s);
		jobQueue.rescheduleTasksOnNode(s);
	    } catch (IOException e) {
		log.error(e.getMessage(), e);
		workerManager.removeWorker(s);
		jobQueue.rescheduleTasksOnNode(s);
	    } finally {
		try {
		    if (sock != null) {
			sock.close();
		    }
		} catch (IOException e) {
		}
	    }
	}
    }

    public boolean isStopped() {
	return stopped;
    }

    public Properties getConfiguration() {
	return configProps;
    }

    public JobQueue getJobQueue() {
	return jobQueue;
    }

    public WorkerManager getWorkerManager() {
	return workerManager;
    }
}
